package framework.impl;

import agent.impl.ResultsImpl;
import framework.api.*;
import framework.object.ResultSeverity;
import framework.utils.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.Serializable;
import java.util.*;

import static framework.impl.KieBuilderImpl.filterFileInKBase;

public class AbstractKieModule implements InternalKieModule {

    protected final ReleaseId releaseId;
    private final Map<String, KnowledgeBuilder> kBuilders = new HashMap<String, KnowledgeBuilder>();
    private final Map<String, Results> resultsCache = new HashMap<String, Results>();

    private final KieModuleModel kModuleModel;
    private static final Logger log = LoggerFactory.getLogger(AbstractKieModule.class);

    protected Map<String, CompilationCache> compilationCache = new HashMap<String, CompilationCache>();

    public AbstractKieModule(ReleaseId releaseId, KieModuleModel kModuleModel) {
        this.releaseId = releaseId;
        this.kModuleModel = kModuleModel;
    }

    @Override
    public ReleaseId getReleaseId() {
        return null;
    }

    @Override
    public KieModuleModel getKieModuleModel() {
        return null;
    }

    private static KnowledgeBuilderConfigurationImpl getBuilderConfiguration(KieBaseModelImpl kBaseModel, KieProject kieProject, AbstractKieModule kModule) {
        KnowledgeBuilderConfigurationImpl pconf = new KnowledgeBuilderConfigurationImpl(kieProject.getClonedClassLoader());
        pconf.setCompilationCache(kModule.getCompilationCache(kBaseModel.getName()));
        setModelPropsOnConf( kBaseModel, pconf );
        return pconf;
    }

    private static void setModelPropsOnConf( KieBaseModelImpl kBaseModel, KnowledgeBuilderConfigurationImpl pconf ) {
        KieModuleModel kModuleModel = kBaseModel.getKModule();
        for (Map.Entry<String, String> entry : kModuleModel.getConfigurationProperties().entrySet()) {
            pconf.setProperty(entry.getKey(), entry.getValue());
        }
    }
    protected CompilationCache getCompilationCache(String kbaseName) {
        CompilationCache cache = compilationCache.get(kbaseName);
        return cache;
    }

    public static class CompilationCache implements Serializable {

    }

    public static KnowledgeBuilder buildKnowledgePackages(KieBaseModelImpl kBaseModel,
                                                          KieProject kieProject,
                                                          ResultsImpl messages ) {
        AbstractKieModule kModule = (AbstractKieModule) kieProject.getKieModuleForKBase(kBaseModel.getName());
        KnowledgeBuilder kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder(getBuilderConfiguration(kBaseModel, kieProject, kModule));
        CompositeKnowledgeBuilder ckbuilder = kbuilder.batch();

        Set<Asset> assets = new HashSet<Asset>();

        boolean allIncludesAreValid = true;
        for (String include : kieProject.getTransitiveIncludes(kBaseModel)) {
            if (StringUtils.isEmpty(include)) {
                continue;
            }
            InternalKieModule includeModule = kieProject.getKieModuleForKBase(include);
            if (includeModule == null) {
                String text = "Unable to build KieBase, could not find include: " + include;
                log.error(text);
                messages.addMessage(Message.Level.ERROR, KieModuleModelImpl.KMODULE_SRC_PATH, text).setKieBaseName( kBaseModel.getName() );
                allIncludesAreValid = false;
                continue;
            }
            addFiles( assets, kieProject.getKieBaseModel(include), includeModule );
        }

        if (!allIncludesAreValid) {
            return null;
        }

        addFiles( assets, kBaseModel, kModule );

        if (assets.isEmpty()) {
            if (kModule instanceof FileKieModule) {
                log.warn("No files found for KieBase " + kBaseModel.getName() + ", searching folder " + kModule.getFile());
            } else {
                log.warn("No files found for KieBase " + kBaseModel.getName());
            }
        } else {
            for (Asset asset : assets) {
                asset.kmodule.addResourceToCompiler(ckbuilder, kBaseModel, asset.name);
            }
        }

        ckbuilder.build();

        if ( kbuilder.hasErrors() ) {
            for ( KnowledgeBuilderError error : kbuilder.getErrors() ) {
                messages.addMessage( error ).setKieBaseName( kBaseModel.getName() );
            }
            log.error("Unable to build KieBaseModel:" + kBaseModel.getName() + "\n" + kbuilder.getErrors().toString());
        }
        if ( kbuilder.hasResults( ResultSeverity.WARNING ) ) {
            for ( KnowledgeBuilderResult warn : kbuilder.getResults( ResultSeverity.WARNING ) ) {
                messages.addMessage( warn ).setKieBaseName( kBaseModel.getName() );
            }
            log.warn( "Warning : " + kBaseModel.getName() + "\n" + kbuilder.getResults( ResultSeverity.WARNING ).toString() );
        }

        // cache KnowledgeBuilder and results
        kModule.cacheKnowledgeBuilderForKieBase(kBaseModel.getName(), kbuilder);
        kModule.cacheResultsForKieBase(kBaseModel.getName(), messages);

        return kbuilder;
    }

    public void cacheKnowledgeBuilderForKieBase(String kieBaseName, KnowledgeBuilder kbuilder) {
        kBuilders.put(kieBaseName, kbuilder);
    }

    public void cacheResultsForKieBase(String kieBaseName, Results results) {
        resultsCache.put(kieBaseName, results);
    }

    private static void addFiles(Set<Asset> assets,
                                 KieBaseModel kieBaseModel,
                                 InternalKieModule kieModule) {
        for (String fileName : kieModule.getFileNames()) {
            if (!fileName.startsWith(".") && !fileName.endsWith(".properties") && filterFileInKBase(kieModule, kieBaseModel, fileName)) {
                assets.add(new Asset( kieModule, fileName ));
            }
        }
    }

    private static class Asset {

        private final InternalKieModule kmodule;
        private final String name;

        private Asset( InternalKieModule kmodule, String name ) {
            this.kmodule = kmodule;
            this.name = name;
        }

    }

    @Override
    public Collection<KnowledgePackage> getKnowledgePackagesForKieBase(String kieBaseName) {
        return null;
    }

    @Override
    public Collection<String> getFileNames() {
        return null;
    }

    @Override
    public ResourceConfiguration getResourceConfiguration(String fileName) {
        return null;
    }

    @Override
    public boolean addResourceToCompiler(CompositeKnowledgeBuilder ckbuilder, KieBaseModel kieBaseModel, String fileName) {
        return false;
    }

    @Override
    public File getFile() {
        return null;
    }
}
